// randomColor by David Merfield under the CC0 license
// https://github.com/davidmerfield/randomColor/

// Seed to get repeatable colors
var seed = null;

// Shared color dictionary
var colorDictionary = {};

// Populate the color dictionary
loadColorBounds();

// check if a range is taken
var colorRanges = [];

export function randomColor (options) {

  options = options || {};

  // Check if there is a seed and ensure it's an
  // integer. Otherwise, reset the seed value.
  if (options.seed !== undefined && options.seed !== null && options.seed === parseInt(options.seed, 10)) {
    seed = options.seed;

  // A string was passed as a seed
  } else if (typeof options.seed === 'string') {
    seed = stringToInteger(options.seed);

  // Something was passed as a seed but it wasn't an integer or string
  } else if (options.seed !== undefined && options.seed !== null) {
    throw new TypeError('The seed value must be an integer or string');

  // No seed, reset the value outside.
  } else {
    seed = null;
  }

  var H,S,B;

  // Check if we need to generate multiple colors
  if (options.count !== null && options.count !== undefined) {

    var totalColors = options.count,
        colors = [];
    // Value false at index i means the range i is not taken yet.
    for (var i = 0; i < options.count; i++) {
      colorRanges.push(false)
      }
    options.count = null;

    while (totalColors > colors.length) {

      // Since we're generating multiple colors,
      // incremement the seed. Otherwise we'd just
      // generate the same color each time...
      if (seed && options.seed) options.seed += 1;

      colors.push(randomColor(options));
    }

    options.count = totalColors;

    return colors;
  }

  // First we pick a hue (H)
  H = pickHue(options);

  // Then use H to determine saturation (S)
  S = pickSaturation(H, options);

  // Then use S and H to determine brightness (B).
  B = pickBrightness(H, S, options);

  // Then we return the HSB color in the desired format
  return setFormat([H,S,B], options);
};

function pickHue(options) {
  if (colorRanges.length > 0) {
    var hueRange = getRealHueRange(options.hue)

    var hue = randomWithin(hueRange)

    //Each of colorRanges.length ranges has a length equal approximatelly one step
    var step = (hueRange[1] - hueRange[0]) / colorRanges.length

    var j = parseInt((hue - hueRange[0]) / step)

    //Check if the range j is taken
    if (colorRanges[j] === true) {
      j = (j + 2) % colorRanges.length
    }
    else {
      colorRanges[j] = true
         }

    var min = (hueRange[0] + j * step) % 359,
        max = (hueRange[0] + (j + 1) * step) % 359;

    hueRange = [min, max]

    hue = randomWithin(hueRange)

    if (hue < 0) {hue = 360 + hue;}
    return hue
  }
  else {
    var hueRange = getHueRange(options.hue)

    hue = randomWithin(hueRange);
    // Instead of storing red as two seperate ranges,
    // we group them, using negative numbers
    if (hue < 0) {
      hue = 360 + hue;
    }

    return hue;
  }
}

function pickSaturation (hue, options) {

  if (options.hue === 'monochrome') {
    return 0;
  }

  if (options.luminosity === 'random') {
    return randomWithin([0,100]);
  }

  var saturationRange = getSaturationRange(hue);

  var sMin = saturationRange[0],
      sMax = saturationRange[1];

  switch (options.luminosity) {

    case 'bright':
      sMin = 55;
      break;

    case 'dark':
      sMin = sMax - 10;
      break;

    case 'light':
      sMax = 55;
      break;
 }

  return randomWithin([sMin, sMax]);

}

function pickBrightness (H, S, options) {

  var bMin = getMinimumBrightness(H, S),
      bMax = 100;

  switch (options.luminosity) {

    case 'dark':
      bMax = bMin + 20;
      break;

    case 'light':
      bMin = (bMax + bMin)/2;
      break;

    case 'random':
      bMin = 0;
      bMax = 100;
      break;
  }

  return randomWithin([bMin, bMax]);
}

function setFormat (hsv, options) {

  switch (options.format) {

    case 'hsvArray':
      return hsv;

    case 'hslArray':
      return HSVtoHSL(hsv);

    case 'hsl':
      var hsl = HSVtoHSL(hsv);
      return 'hsl('+hsl[0]+', '+hsl[1]+'%, '+hsl[2]+'%)';

    case 'hsla':
      var hslColor = HSVtoHSL(hsv);
      var alpha = options.alpha || Math.random();
      return 'hsla('+hslColor[0]+', '+hslColor[1]+'%, '+hslColor[2]+'%, ' + alpha + ')';

    case 'rgbArray':
      return HSVtoRGB(hsv);

    case 'rgb':
      var rgb = HSVtoRGB(hsv);
      return 'rgb(' + rgb.join(', ') + ')';

    case 'rgba':
      var rgbColor = HSVtoRGB(hsv);
      var alpha = options.alpha || Math.random();
      return 'rgba(' + rgbColor.join(', ') + ', ' + alpha + ')';

    default:
      return HSVtoHex(hsv);
  }

}

function getMinimumBrightness(H, S) {

  var lowerBounds = getColorInfo(H).lowerBounds;

  for (var i = 0; i < lowerBounds.length - 1; i++) {

    var s1 = lowerBounds[i][0],
        v1 = lowerBounds[i][1];

    var s2 = lowerBounds[i+1][0],
        v2 = lowerBounds[i+1][1];

    if (S >= s1 && S <= s2) {

       var m = (v2 - v1)/(s2 - s1),
           b = v1 - m*s1;

       return m*S + b;
    }

  }

  return 0;
}

function getHueRange (colorInput) {

  if (typeof parseInt(colorInput) === 'number') {

    var number = parseInt(colorInput);

    if (number < 360 && number > 0) {
      return [number, number];
    }

  }

  if (typeof colorInput === 'string') {

    if (colorDictionary[colorInput]) {
      var color = colorDictionary[colorInput];
      if (color.hueRange) {return color.hueRange;}
    } else if (colorInput.match(/^#?([0-9A-F]{3}|[0-9A-F]{6})$/i)) {
      var hue = HexToHSB(colorInput)[0];
      return [ hue, hue ];
    }
  }

  return [0,360];

}

function getSaturationRange (hue) {
  return getColorInfo(hue).saturationRange;
}

function getColorInfo (hue) {

  // Maps red colors to make picking hue easier
  if (hue >= 334 && hue <= 360) {
    hue-= 360;
  }

  for (var colorName in colorDictionary) {
     var color = colorDictionary[colorName];
     if (color.hueRange &&
         hue >= color.hueRange[0] &&
         hue <= color.hueRange[1]) {
        return colorDictionary[colorName];
     }
  } return 'Color not found';
}

function randomWithin (range) {
  if (seed === null) {
    //generate random evenly destinct number from : https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
    var golden_ratio = 0.618033988749895
    var r=Math.random()
    r += golden_ratio
    r %= 1
    return Math.floor(range[0] + r*(range[1] + 1 - range[0]));
  } else {
    //Seeded random algorithm from http://indiegamr.com/generate-repeatable-random-numbers-in-js/
    var max = range[1] || 1;
    var min = range[0] || 0;
    seed = (seed * 9301 + 49297) % 233280;
    var rnd = seed / 233280.0;
    return Math.floor(min + rnd * (max - min));
}
}

function HSVtoHex (hsv){

  var rgb = HSVtoRGB(hsv);

  function componentToHex(c) {
      var hex = c.toString(16);
      return hex.length == 1 ? '0' + hex : hex;
  }

  var hex = '#' + componentToHex(rgb[0]) + componentToHex(rgb[1]) + componentToHex(rgb[2]);

  return hex;

}

function defineColor (name, hueRange, lowerBounds) {

  var sMin = lowerBounds[0][0],
      sMax = lowerBounds[lowerBounds.length - 1][0],

      bMin = lowerBounds[lowerBounds.length - 1][1],
      bMax = lowerBounds[0][1];

  colorDictionary[name] = {
    hueRange: hueRange,
    lowerBounds: lowerBounds,
    saturationRange: [sMin, sMax],
    brightnessRange: [bMin, bMax]
  };

}

function loadColorBounds () {

  defineColor(
    'monochrome',
    null,
    [[0,0],[100,0]]
  );

  defineColor(
    'red',
    [-26,18],
    [[20,100],[30,92],[40,89],[50,85],[60,78],[70,70],[80,60],[90,55],[100,50]]
  );

  defineColor(
    'orange',
    [19,46],
    [[20,100],[30,93],[40,88],[50,86],[60,85],[70,70],[100,70]]
  );

  defineColor(
    'yellow',
    [47,62],
    [[25,100],[40,94],[50,89],[60,86],[70,84],[80,82],[90,80],[100,75]]
  );

  defineColor(
    'green',
    [63,178],
    [[30,100],[40,90],[50,85],[60,81],[70,74],[80,64],[90,50],[100,40]]
  );

  defineColor(
    'blue',
    [179, 257],
    [[20,100],[30,86],[40,80],[50,74],[60,60],[70,52],[80,44],[90,39],[100,35]]
  );

  defineColor(
    'purple',
    [258, 282],
    [[20,100],[30,87],[40,79],[50,70],[60,65],[70,59],[80,52],[90,45],[100,42]]
  );

  defineColor(
    'pink',
    [283, 334],
    [[20,100],[30,90],[40,86],[60,84],[80,80],[90,75],[100,73]]
  );

}

function HSVtoRGB (hsv) {

  // this doesn't work for the values of 0 and 360
  // here's the hacky fix
  var h = hsv[0];
  if (h === 0) {h = 1;}
  if (h === 360) {h = 359;}

  // Rebase the h,s,v values
  h = h/360;
  var s = hsv[1]/100,
      v = hsv[2]/100;

  var h_i = Math.floor(h*6),
    f = h * 6 - h_i,
    p = v * (1 - s),
    q = v * (1 - f*s),
    t = v * (1 - (1 - f)*s),
    r = 256,
    g = 256,
    b = 256;

  switch(h_i) {
    case 0: r = v; g = t; b = p;  break;
    case 1: r = q; g = v; b = p;  break;
    case 2: r = p; g = v; b = t;  break;
    case 3: r = p; g = q; b = v;  break;
    case 4: r = t; g = p; b = v;  break;
    case 5: r = v; g = p; b = q;  break;
  }

  var result = [Math.floor(r*255), Math.floor(g*255), Math.floor(b*255)];
  return result;
}

function HexToHSB (hex) {
  hex = hex.replace(/^#/, '');
  hex = hex.length === 3 ? hex.replace(/(.)/g, '$1$1') : hex;

  var red = parseInt(hex.substr(0, 2), 16) / 255,
        green = parseInt(hex.substr(2, 2), 16) / 255,
        blue = parseInt(hex.substr(4, 2), 16) / 255;

  var cMax = Math.max(red, green, blue),
        delta = cMax - Math.min(red, green, blue),
        saturation = cMax ? (delta / cMax) : 0;

  switch (cMax) {
    case red: return [ 60 * (((green - blue) / delta) % 6) || 0, saturation, cMax ];
    case green: return [ 60 * (((blue - red) / delta) + 2) || 0, saturation, cMax ];
    case blue: return [ 60 * (((red - green) / delta) + 4) || 0, saturation, cMax ];
  }
}

function HSVtoHSL (hsv) {
  var h = hsv[0],
    s = hsv[1]/100,
    v = hsv[2]/100,
    k = (2-s)*v;

  return [
    h,
    Math.round(s*v / (k<1 ? k : 2-k) * 10000) / 100,
    k/2 * 100
  ];
}

function stringToInteger (string) {
  var total = 0
  for (var i = 0; i !== string.length; i++) {
    if (total >= Number.MAX_SAFE_INTEGER) break;
    total += string.charCodeAt(i)
  }
  return total
}

// get The range of given hue when options.count!=0
function getRealHueRange(colorHue)
{ if (!isNaN(colorHue)) {
  var number = parseInt(colorHue);

  if (number < 360 && number > 0) {
    return getColorInfo(colorHue).hueRange
  }
}
  else if (typeof colorHue === 'string') {

    if (colorDictionary[colorHue]) {
      var color = colorDictionary[colorHue];

      if (color.hueRange) {
        return color.hueRange
     }
  } else if (colorHue.match(/^#?([0-9A-F]{3}|[0-9A-F]{6})$/i)) {
      var hue = HexToHSB(colorHue)[0]
      return getColorInfo(hue).hueRange
  }
}

  return [0,360]
}
